﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        Camera cam = new Camera();
        Projection p = new Projection();
        ObjData[] objs;
        Face[] faces;

        private void Form1_Load(object sender, EventArgs e)
        {
            this.MouseDown += Form1_MouseDown;
            this.MouseMove += Form1_MouseMove;
            this.MouseUp += Form1_MouseUp;
            this.KeyDown += Form1_KeyDown;
            this.KeyUp += Form1_KeyUp;

            p.init(640, 480);
            //cpu 放矩阵单元（复用乘法器）

            camp = new Matrix();
            camp.init(4);

            tcamp = new Matrix();
            tcamp.init(4);

            objs = new ObjData[3];
            objs[0] = Class1.readObj("cube");
            objs[1] = Class1.readObj("aaa");
            objs[2] = Class1.readObj("sphere");

            float a = 10;
            float b = 0;
            float inf = a / b;
            float q1 = 1;
            float p1 = -1;
            int q2 = (int)(q1 * inf);
            int p2 = (int)(p1 * inf);

            int maxFace = 0;
            for (int k = 0; k < objs.Length; k++)
            {
                maxFace += objs[k].faceCount;
            }

            faces = new Face[maxFace];


        }

        bool key_w = false;
        bool key_a = false;
        bool key_s = false;
        bool key_d = false;
        bool key_e = false;
        bool key_c = false;
        void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.W) { key_w = true; }
            if (e.KeyCode == Keys.A) { key_a = true; }
            if (e.KeyCode == Keys.S) { key_s = true; }
            if (e.KeyCode == Keys.D) { key_d = true; }
            if (e.KeyCode == Keys.E) { key_e = true; }
            if (e.KeyCode == Keys.C) { key_c = true; }
        }

        void Form1_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.W) { key_w = false; }
            if (e.KeyCode == Keys.A) { key_a = false; }
            if (e.KeyCode == Keys.S) { key_s = false; }
            if (e.KeyCode == Keys.D) { key_d = false; }
            if (e.KeyCode == Keys.E) { key_e = false; }
            if (e.KeyCode == Keys.C) { key_c = false; }
        }

        
            Matrix camp;

            Matrix tcamp;
        void render()
        {
            Matrix camm = cam.cameraMatrix();
            camm.multi(p.m, camp);

            //int index = 0;
            //TODO tcamp transform
            for (int k = 0; k < objs.Length; k++)
            {
                //camp.multi(objs[k].matrix, tcamp);
                objs[k].matrix.multi(camp, tcamp);

                objs[k].vertexes.multi(tcamp, objs[k].result);//TODO tcamp
                //index += objs[k].vertexes.lines;

            }



            for (int k = 0; k < objs.Length; k++)
            {
                float[] vertexes = objs[k].result.m;
                for (var i = 0; i < objs[k].result.m.Length; i += 4)
                {
                    if (vertexes[i + 2] > 0)
                    {
                        var n = 1 / vertexes[i + 3];
                        vertexes[i + 0] *= n;
                        vertexes[i + 1] *= n;
                        //vertexes[i + 2] *= n;

                        vertexes[i + 0] = vertexes[i + 0] * p.half_w + p.half_w;
                        vertexes[i + 1] = -vertexes[i + 1] * p.half_h + p.half_h;
                    }
                }

                //for (int i = 0; i < objs[k].vertexes.lines * 4; i += 4)
                //{
                //    Console.WriteLine("K:" + k + "i:" + i + "," + objs[k].result.m[i + 0] + "," + objs[k].result.m[i + 1] + "," + objs[k].result.m[i + 2] + "," + objs[k].result.m[i + 3]);
                //}
            }

            Bitmap b = new Bitmap((int)p.w, (int)p.h);
            Graphics g = Graphics.FromImage(b);

            int findex = 0;
            for (int k = 0; k < objs.Length; k++)
            {
                for (var i = 0; i < objs[k].faceVerticles.Length; i += 3)
                {
                    var v0 = objs[k].faceVerticles[i + 0];
                    var v1 = objs[k].faceVerticles[i + 1];
                    var v2 = objs[k].faceVerticles[i + 2];

                    var nv0 = objs[k].faceTexture[i + 0];
                    var nv1 = objs[k].faceTexture[i + 1];
                    var nv2 = objs[k].faceTexture[i + 2];

                    float[] vertexes = objs[k].result.m;
                    if (vertexes[v0 * 4 + 2] > 0 && vertexes[v1 * 4 + 2] > 0 && vertexes[v2 * 4 + 2] > 0)
                    {
                        var v0x = vertexes[v0 * 4 + 0];
                        var v0y = vertexes[v0 * 4 + 1];
                        var v1x = vertexes[v1 * 4 + 0];
                        var v1y = vertexes[v1 * 4 + 1];
                        var v2x = vertexes[v2 * 4 + 0];
                        var v2y = vertexes[v2 * 4 + 1];

                        var vcz = (v1x - v0x) * (v2y - v0y) - (v1y - v0y) * (v2x - v0x);

                        if (vcz > 0)
                        {
                            //g.DrawLine(pen, v0x, v0y, v1x, v1y);
                            //g.DrawLine(pen, v1x, v1y, v2x, v2y);
                            //g.DrawLine(pen, v2x, v2y, v0x, v0y);
                            var u0x = objs[k].textureMap[nv0 * 2 + 0];
                            var u0y = objs[k].textureMap[nv0 * 2 + 1];
                            var u1x = objs[k].textureMap[nv1 * 2 + 0];
                            var u1y = objs[k].textureMap[nv1 * 2 + 1];
                            var u2x = objs[k].textureMap[nv2 * 2 + 0];
                            var u2y = objs[k].textureMap[nv2 * 2 + 1];

                            Face f = new Face();
                            f.texture = objs[k].texture;
                            f.v0x = v0x;
                            f.v0y = v0y;
                            f.v1x = v1x;
                            f.v1y = v1y;
                            f.v2x = v2x;
                            f.v2y = v2y;
                            f.u0x = u0x;
                            f.u0y = u0y;
                            f.u1x = u1x;
                            f.u1y = u1y;
                            f.u2x = u2x;
                            f.u2y = u2y;
                            f.z = vertexes[v0 * 4 + 2];
                            this.faces[findex++] = f;
                        }
                    }
                }
            }

            quickSort(this.faces, 0, findex - 1);

            for (int i = 0; i < findex; i++)
            {
                Face f = this.faces[i];
                FillTriangle(b, f.texture, f.v0x, f.v0y, f.v1x, f.v1y, f.v2x, f.v2y, f.u0x, f.u0y, f.u1x, f.u1y, f.u2x, f.u2y);
            }


            g.Flush();
            g.Dispose();
            this.BackgroundImage = b;
            this.BackgroundImageLayout = ImageLayout.None;
            //this.pictureBox1.Image = b;
        }


        void quickSort(Face[] arr, int left, int right)
        {
            int i = left;
            int j = right;
            Face pivot = arr[left];
            for (; ; )
            {
                while (i < j && (arr[j].z < pivot.z))
                {
                    j--;
                }
                while (i < j && (arr[i].z >= pivot.z))
                {
                    i++;
                }
                if (i < j)
                {
                    Face temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
                else
                {
                    break;
                }
            }

            {
                Face temp = arr[i];
                arr[i] = arr[left];
                arr[left] = temp;
            }

            if (left < right)
            {
                quickSort(arr, left, i - 1);
            }
            if (left < right)
            {
                quickSort(arr, i + 1, right);
            }
        }
        float minval = float.MaxValue;
        float maxval = float.MinValue;
        void log(float val)
        {
            if (val > maxval) maxval = val;
            if (val < minval) minval = val;
        }



        int draw3d_long_param1;
        int draw3d_long_param2;
        int draw3d_long_param3;
        int draw3d_long_param4;
        int draw3d_long_param5;
        int draw3d_long_param6;
        int draw3d_long_param7;
        int draw3d_long_param8;
        int draw3d_long_param9;
        void push(int val)
        {
            draw3d_long_param9 = draw3d_long_param8;
            draw3d_long_param8 = draw3d_long_param7;
            draw3d_long_param7 = draw3d_long_param6;
            draw3d_long_param6 = draw3d_long_param5;
            draw3d_long_param5 = draw3d_long_param4;
            draw3d_long_param4 = draw3d_long_param3;
            draw3d_long_param3 = draw3d_long_param2;
            draw3d_long_param2 = draw3d_long_param1;
            draw3d_long_param1 = val;
        }
        void shift()
        {
            int temp = draw3d_long_param9;
            draw3d_long_param9 = draw3d_long_param8;
            draw3d_long_param8 = draw3d_long_param7;
            draw3d_long_param7 = draw3d_long_param6;
            draw3d_long_param6 = draw3d_long_param5;
            draw3d_long_param5 = draw3d_long_param4;
            draw3d_long_param4 = draw3d_long_param3;
            draw3d_long_param3 = draw3d_long_param2;
            draw3d_long_param2 = draw3d_long_param1;
            draw3d_long_param1 = temp;
        }


        void FillTriangle(Bitmap bitmap, int[] texture, float v0x, float v0y, float v1x, float v1y, float v2x, float v2y, float u0x, float u0y, float u1x, float u1y, float u2x, float u2y)
        {

            float ww = 1.0f / ((v2x * (v1y - v0y) - v0x * (v1y - v0y)) - v2y * (v1x - v0x) + v0y * (v1x - v0x));
            
            float qa = (v2y - v1y) * ww;
            float qb = (v0y - v2y) * ww;
            float qc = (v1y - v0y) * ww;


            int qqa = (int)(qa * 1048576.0f);
            int qqb = (int)(qb * 1048576.0f);
            int qqc = (int)(qc * 1048576.0f);


            float vqa = -1.0f / qa;
            float vqb = -1.0f / qb;
            float vqc = -1.0f / qc;



            float pa = (v1x - v2x) * ww;
            float pb = (v2x - v0x) * ww;
            float pc = (v0x - v1x) * ww;

            float ra = (v1y * (v2x - v1x) - v1x * (v2y - v1y)) * ww;
            float rb = (v2y * (v0x - v2x) - v2x * (v0y - v2y)) * ww;
            float rc = (v0y * (v1x - v0x) - v0x * (v1y - v0y)) * ww;

            log(u0x);

            int uv0x = (int)(u0x * 32768.0f);
            int uv0y = (int)(u0y * 32768.0f);
            int uv1x = (int)(u1x * 32768.0f);
            int uv1y = (int)(u1y * 32768.0f);
            int uv2x = (int)(u2x * 32768.0f);
            int uv2y = (int)(u2y * 32768.0f);

            int v = (qa >= 0 ? 0 : 4) | (qb >= 0 ? 0 : 2) | (qc >= 0 ? 0 : 1);

            int width = (int)p.w;
            int height = (int)p.h;

            int x0 = (int)v0x;
            if ((int)v1x < x0)
            {
                x0 = (int)v1x;
            }
            if ((int)v2x < x0)
            {
                x0 = (int)v2x;
            }
            if (x0 < 0)
            {
                x0 = 0;
            }
            if (x0 > width)
            {
                return;
            }

            int y0 = (int)v0y;
            if ((int)v1y < y0)
            {
                y0 = (int)v1y;
            }
            if ((int)v2y < y0)
            {
                y0 = (int)v2y;
            }
            if (y0 < 0)
            {
                y0 = 0;
            }
            if (y0 > height)
            {
                return;
            }


            int x1 = (int)v0x;
            if ((int)v1x > x1)
            {
                x1 = (int)v1x;
            }
            if ((int)v2x > x1)
            {
                x1 = (int)v2x;
            }
            if (x1 > width - 1)
            {
                x1 = width - 1;
            }


            int y1 = (int)v0y;
            if ((int)v1y > y1)
            {
                y1 = (int)v1y;
            }
            if ((int)v2y > y1)
            {
                y1 = (int)v2y;
            }
            if (y1 > height - 1)
            {
                y1 = height - 1;
            }
            int idx = 0;

            //Console.WriteLine("xy:" + x0 + "," + y0 + "," + x1 + "," + y1);


            push(qqa);
            push(qqb);
            push(qqc);
            push(uv0x);
            push(uv1x);
            push(uv2x);
            push(uv0y);
            push(uv1y);
            push(uv2y);


            for (int y = y0; y <= y1; y++)
            { //       only check for points that are inside the screen
                float vy = y;// +0.5f;

                float ma = vy * pa + ra;
                float mb = vy * pb + rb;
                float mc = vy * pc + rc;

                int mma = (int)(ma * 1048576.0f);
                int mmb = (int)(mb * 1048576.0f);
                int mmc = (int)(mc * 1048576.0f);

                int ima = (int)(ma * vqa);
                int imb = (int)(mb * vqb);
                int imc = (int)(mc * vqc);

                int min = 0;
                int max = 0;

                switch (v)
                {
                    case 1: min = Math.Max(ima, imb); max = imc; break;
                    case 2: min = Math.Max(ima, imc); max = imb; break;
                    case 3: min = ima; max = Math.Min(imb, imc); break;
                    case 4: min = Math.Max(imb, imc); max = ima; break;
                    case 5: min = imb; max = Math.Min(ima, imc); break;
                    case 6: min = imc; max = Math.Min(ima, imb); break;
                }

                if (min >= width)
                {
                    continue;
                }
                if (max < 0)
                {
                    continue;
                }
                if (max < min)
                {
                    continue;
                }

                if (min < 0)
                {
                    min = 0;
                }
                if (max >= width)
                {
                    max = width - 1;
                }

                //Console.WriteLine(idx+",y:" + y + "," + min + "," + max );
                idx++;

                /*
                for (int x = min; x <= max; x++)
                { //   and inside the triangle bounding box
                    int a = (x * qqa + mma) >> (20 - 15);
                    int b = (x * qqb + mmb) >> (20 - 15);
                    int c = (x * qqc + mmc) >> (20 - 15);

                    int xx = (uv0x * a + uv1x * b + uv2x * c) >> (30 - 8);
                    int yy = (uv0y * a + uv1y * b + uv2y * c) >> (30 - 8);
                    
                    if (xx > 255) xx = 255;
                    if (xx < 0) xx = 0;
                    if (yy > 255) yy = 255;
                    if (yy < 0) yy = 0;
                    //DrawPoint(x, y, model.material.texture.data[xx + yy * 256] , 0);
                    bitmap.SetPixel(x, y, Color.FromArgb(texture[xx + yy * 256]));
                }
                */


                for (int x = min; x <= max; x++)
                { //   and inside the triangle bounding box

                    int a = (x * draw3d_long_param9 + mma) >> (20 - 15);
                    shift();
                    int b = (x * draw3d_long_param9 + mmb) >> (20 - 15);
                    shift();
                    int c = (x * draw3d_long_param9 + mmc) >> (20 - 15);
                    shift();

                    int xx = 0;
                    xx = xx + a * draw3d_long_param9;
                    shift();
                    xx = xx + b * draw3d_long_param9;
                    shift();
                    xx = xx + c * draw3d_long_param9;
                    shift();
                    xx = xx >> (30 - 8);

                    int yy = 0;
                    yy = yy + a * draw3d_long_param9;
                    shift();
                    yy = yy + b * draw3d_long_param9;
                    shift();
                    yy = yy + c * draw3d_long_param9;
                    shift();

                    yy = yy >> (30 - 8);

                    if (xx > 255) xx = 255;
                    if (xx < 0) xx = 0;
                    if (yy > 255) yy = 255;
                    if (yy < 0) yy = 0;

                    bitmap.SetPixel(x, y, Color.FromArgb(texture[xx + yy * 256]));
                }


            }
        }

        Boolean down = false;
        int X0 = 0;
        int Y0 = 0;
        float oldDirection = 0;
        float oldAngle = 0;
        void Form1_MouseDown(object sender, MouseEventArgs e)
        {
            X0 = e.X;
            Y0 = e.Y;
            oldDirection = cam.direction;
            oldAngle = cam.angle;
            down = true;
        }
        void Form1_MouseMove(object sender, MouseEventArgs e)
        {
            if (down)
            {
                var x = e.X - X0;
                var y = e.Y - Y0;
                cam.direction = oldDirection + x * cam.rotation_speed;
                cam.angle = oldAngle + y * cam.rotation_speed;
                float limit = 3.1415926f / 2.0f;
                if (cam.angle > limit)
                {
                    cam.angle = limit;
                }
                if (cam.angle < -limit)
                {
                    cam.angle = -limit;
                }
            }
        }

        void Form1_MouseUp(object sender, MouseEventArgs e)
        {
            down = false;
        }

        float pos = 0;
        float rot = 0;
        float rot2 = 0;
        private void timer1_Tick(object sender, EventArgs e)
        {
            if (key_w)
            {
                cam.x += (float)(cam.moving_speed * Math.Sin(cam.direction) * Math.Cos(-cam.angle));
                cam.y += (float)(cam.moving_speed * Math.Sin(-cam.angle));
                cam.z += (float)(cam.moving_speed * Math.Cos(cam.direction) * Math.Cos(-cam.angle));
            }
            if (key_s)
            {
                cam.x -= (float)(cam.moving_speed * Math.Sin(cam.direction) * Math.Cos(-cam.angle));
                cam.y -= (float)(cam.moving_speed * Math.Sin(-cam.angle));
                cam.z -= (float)(cam.moving_speed * Math.Cos(cam.direction) * Math.Cos(-cam.angle));
            }
            if (key_a)
            {
                cam.x += (float)(cam.moving_speed * Math.Sin(cam.direction - Math.PI * 0.5) * 0.5);
                cam.z += (float)(cam.moving_speed * Math.Cos(cam.direction - Math.PI * 0.5) * 0.5);
            }
            if (key_d)
            {
                cam.x += (float)(cam.moving_speed * Math.Sin(cam.direction + Math.PI * 0.5) * 0.5);
                cam.z += (float)(cam.moving_speed * Math.Cos(cam.direction + Math.PI * 0.5) * 0.5);
            }
            if (key_e)
            {
                cam.y += cam.moving_speed;
            }
            if (key_c)
            {
                cam.y -= cam.moving_speed;
            }


            pos = pos + 0.5f;
            if (pos > 50) pos = -50;

            rot2 = rot2 + 0.03f;
            if (rot2 > 3.1416*2) rot2 = 0;

            objs[0].matrix.init();
            objs[0].matrix.scale(1.0f, 1.0f, 1.0f);
            objs[0].matrix.move(pos, 2.1f, 2.1f);


            objs[1].matrix.init();
            objs[1].matrix.scale(0.2f, 0.2f, 0.2f);
            //objs[0].matrix.scale(0.5f, 0.5f, 0.5f);
            //objs[0].matrix.rotateY(rot);
            
            objs[2].matrix.init();
            objs[2].matrix.scale(2.0f, 2.0f, 2.0f);
            objs[2].matrix.rotateY(-rot2);
            objs[2].matrix.move(3.0f, 0, 0);
            objs[2].matrix.rotateY(rot2);


            Console.WriteLine(minval + "," + maxval);
            minval = int.MaxValue;
            maxval = int.MinValue;



            render();
        }

        static string[] convt = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
        public static string getHex8(int val)//"ll"+"hh"
        {
            return convt[((val >> 28) & 0x0F)] + convt[((val >> 24) & 0x0F)] + convt[((val >> 20) & 0x0F)] + convt[((val >> 16) & 0x0F)] + convt[((val >> 12) & 0x0F)] + convt[((val >> 8) & 0x0F)] + convt[((val >> 4) & 0x0F)] + convt[((val >> 0) & 0x0F)];
        }
    }
}